/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * =======================================================================
 *
 * Lightmaps and dynamic lighting
 *
 * =======================================================================
 */  

#include "header/local.h"

#define DLIGHT_CUTOFF   64
 
int r_dlightframecount;
vec3_t pointcolor;
cplane_t *lightplane; /* used as shadow plane */
vec3_t lightspot;
static float s_blocklights [ 34 * 34 * 3 ];
   
void
R_RenderDlight ( dlight_t *light )
{
	int i, j;
	float a;
	vec3_t v;
	float rad;

	rad = light->intensity * 0.35;

	VectorSubtract( light->origin, r_origin, v );

	qglBegin( GL_TRIANGLE_FAN );
	qglColor3f( light->color [ 0 ] * 0.2, light->color [ 1 ] * 0.2, light->color [ 2 ] * 0.2 );

	for ( i = 0; i < 3; i++ )
	{
		v [ i ] = light->origin [ i ] - vpn [ i ] * rad;
	}

	qglVertex3fv( v );
	qglColor3f( 0, 0, 0 );

	for ( i = 16; i >= 0; i-- )
	{
		a = i / 16.0 * M_PI * 2;

		for ( j = 0; j < 3; j++ )
		{
			v [ j ] = light->origin [ j ] + vright [ j ] * cos( a ) * rad
					  + vup [ j ] * sin( a ) * rad;
		}

		qglVertex3fv( v );
	}

	qglEnd();
}

void
R_RenderDlights ( void )
{
	int i;
	dlight_t    *l;

	if ( !gl_flashblend->value )
	{
		return;
	}

	/* because the count hasn't advanced yet for this frame */
	r_dlightframecount = r_framecount + 1;
	
	qglDepthMask( 0 );
	qglDisable( GL_TEXTURE_2D );
	qglShadeModel( GL_SMOOTH );
	qglEnable( GL_BLEND );
	qglBlendFunc( GL_ONE, GL_ONE );

	l = r_newrefdef.dlights;

	for ( i = 0; i < r_newrefdef.num_dlights; i++, l++ )
	{
		R_RenderDlight( l );
	}

	qglColor3f( 1, 1, 1 );
	qglDisable( GL_BLEND );
	qglEnable( GL_TEXTURE_2D );
	qglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	qglDepthMask( 1 );
}

void
R_MarkLights ( dlight_t *light, int bit, mnode_t *node )
{
	cplane_t    *splitplane;
	float dist;
	msurface_t  *surf;
	int i;
	int sidebit;

	if ( node->contents != -1 )
	{
		return;
	}

	splitplane = node->plane;
	dist = DotProduct( light->origin, splitplane->normal ) - splitplane->dist;

	if ( dist > light->intensity - DLIGHT_CUTOFF )
	{
		R_MarkLights( light, bit, node->children [ 0 ] );
		return;
	}

	if ( dist < -light->intensity + DLIGHT_CUTOFF )
	{
		R_MarkLights( light, bit, node->children [ 1 ] );
		return;
	}

	/* mark the polygons */
	surf = r_worldmodel->surfaces + node->firstsurface;

	for ( i = 0; i < node->numsurfaces; i++, surf++ )
	{
		dist = DotProduct (light->origin, surf->plane->normal) - surf->plane->dist;

        if (dist >= 0)
			sidebit = 0;
		else
			sidebit = SURF_PLANEBACK;


		if ( (surf->flags & SURF_PLANEBACK) != sidebit )
			continue;

		if ( surf->dlightframe != r_dlightframecount )
		{
			surf->dlightbits = 0;
			surf->dlightframe = r_dlightframecount;
		}

		surf->dlightbits |= bit;
	}

	R_MarkLights( light, bit, node->children [ 0 ] );
	R_MarkLights( light, bit, node->children [ 1 ] );
}

void
R_PushDlights ( void )
{
	int i;
	dlight_t    *l;

	if ( gl_flashblend->value )
	{
		return;
	}

	/* because the count hasn't advanced yet for this frame */
	r_dlightframecount = r_framecount + 1; 
	
	l = r_newrefdef.dlights;

	for ( i = 0; i < r_newrefdef.num_dlights; i++, l++ )
	{
		R_MarkLights( l, 1 << i, r_worldmodel->nodes );
	}
}

int
R_RecursiveLightPoint ( mnode_t *node, vec3_t start, vec3_t end )
{
	float front, back, frac;
	int side;
	cplane_t    *plane;
	vec3_t mid;
	msurface_t  *surf;
	int s, t, ds, dt;
	int i;
	mtexinfo_t  *tex;
	byte        *lightmap;
	int maps;
	int r;

	if ( node->contents != -1 )
	{
		return ( -1 ); /* didn't hit anything */
	}

	/* calculate mid point */
	plane = node->plane;
	front = DotProduct( start, plane->normal ) - plane->dist;
	back = DotProduct( end, plane->normal ) - plane->dist;
	side = front < 0;

	if ( ( back < 0 ) == side )
	{
		return ( R_RecursiveLightPoint( node->children [ side ], start, end ) );
	}

	frac = front / ( front - back );
	mid [ 0 ] = start [ 0 ] + ( end [ 0 ] - start [ 0 ] ) * frac;
	mid [ 1 ] = start [ 1 ] + ( end [ 1 ] - start [ 1 ] ) * frac;
	mid [ 2 ] = start [ 2 ] + ( end [ 2 ] - start [ 2 ] ) * frac;

	/* go down front side */
	r = R_RecursiveLightPoint( node->children [ side ], start, mid );

	if ( r >= 0 )
	{
		return ( r ); /* hit something */
	}

	if ( ( back < 0 ) == side )
	{
		return ( -1 ); /* didn't hit anuthing */
	}

	/* check for impact on this node */
	VectorCopy( mid, lightspot );
	lightplane = plane;

	surf = r_worldmodel->surfaces + node->firstsurface;

	for ( i = 0; i < node->numsurfaces; i++, surf++ )
	{
		if ( surf->flags & ( SURF_DRAWTURB | SURF_DRAWSKY ) )
		{
			continue; /* no lightmaps */
		}

		tex = surf->texinfo;

		s = DotProduct( mid, tex->vecs [ 0 ] ) + tex->vecs [ 0 ] [ 3 ];
		t = DotProduct( mid, tex->vecs [ 1 ] ) + tex->vecs [ 1 ] [ 3 ];

		if ( ( s < surf->texturemins [ 0 ] ) ||
			 ( t < surf->texturemins [ 1 ] ) )
		{
			continue;
		}

		ds = s - surf->texturemins [ 0 ];
		dt = t - surf->texturemins [ 1 ];

		if ( ( ds > surf->extents [ 0 ] ) || ( dt > surf->extents [ 1 ] ) )
		{
			continue;
		}

		if ( !surf->samples )
		{
			return ( 0 );
		}

		ds >>= 4;
		dt >>= 4;

		lightmap = surf->samples;
		VectorCopy( vec3_origin, pointcolor );

		if ( lightmap )
		{
			vec3_t scale;

			lightmap += 3 * ( dt * ( ( surf->extents [ 0 ] >> 4 ) + 1 ) + ds );

			for ( maps = 0; maps < MAXLIGHTMAPS && surf->styles [ maps ] != 255;
				  maps++ )
			{
				for ( i = 0; i < 3; i++ )
				{
					scale [ i ] = gl_modulate->value * r_newrefdef.lightstyles [ surf->styles [ maps ] ].rgb [ i ];
				}

				pointcolor [ 0 ] += lightmap [ 0 ] * scale [ 0 ] * ( 1.0 / 255 );
				pointcolor [ 1 ] += lightmap [ 1 ] * scale [ 1 ] * ( 1.0 / 255 );
				pointcolor [ 2 ] += lightmap [ 2 ] * scale [ 2 ] * ( 1.0 / 255 );
				lightmap += 3 * ( ( surf->extents [ 0 ] >> 4 ) + 1 ) *
							( ( surf->extents [ 1 ] >> 4 ) + 1 );
			}
		}

		return ( 1 );
	}

	/* go down back side */
	return ( R_RecursiveLightPoint( node->children [ !side ], mid, end ) );
}

void
R_LightPoint ( vec3_t p, vec3_t color )
{
	vec3_t end;
	float r;
	int lnum;
	dlight_t    *dl;
	float light;
	vec3_t dist;
	float add;

	if ( !r_worldmodel->lightdata )
	{
		color [ 0 ] = color [ 1 ] = color [ 2 ] = 1.0;
		return;
	}

	end [ 0 ] = p [ 0 ];
	end [ 1 ] = p [ 1 ];
	end [ 2 ] = p [ 2 ] - 2048;

	r = R_RecursiveLightPoint( r_worldmodel->nodes, p, end );

	if ( r == -1 )
	{
		VectorCopy( vec3_origin, color );
	}
	else
	{
		VectorCopy( pointcolor, color );
	}

	/* add dynamic lights */
	light = 0;
	dl = r_newrefdef.dlights;

	for ( lnum = 0; lnum < r_newrefdef.num_dlights; lnum++, dl++ )
	{
		VectorSubtract( currententity->origin,
				dl->origin,
				dist );
		add = dl->intensity - VectorLength( dist );
		add *= ( 1.0 / 256 );

		if ( add > 0 )
		{
			VectorMA( color, add, dl->color, color );
		}
	}

	VectorScale( color, gl_modulate->value, color );
}

void
R_AddDynamicLights ( msurface_t *surf )
{
	int lnum;
	int sd, td;
	float fdist, frad, fminlight;
	vec3_t impact, local;
	int s, t;
	int i;
	int smax, tmax;
	mtexinfo_t  *tex;
	dlight_t    *dl;
	float       *pfBL;
	float fsacc, ftacc;

	smax = ( surf->extents [ 0 ] >> 4 ) + 1;
	tmax = ( surf->extents [ 1 ] >> 4 ) + 1;
	tex = surf->texinfo;

	for ( lnum = 0; lnum < r_newrefdef.num_dlights; lnum++ )
	{
		if ( !( surf->dlightbits & ( 1 << lnum ) ) )
		{
			continue; /* not lit by this light */
		}

		dl = &r_newrefdef.dlights [ lnum ];
		frad = dl->intensity;
		fdist = DotProduct( dl->origin, surf->plane->normal ) -
				surf->plane->dist;
		frad -= fabs( fdist );

		/* rad is now the highest intensity on the plane */
		fminlight = DLIGHT_CUTOFF;

		if ( frad < fminlight )
		{
			continue;
		}

		fminlight = frad - fminlight;

		for ( i = 0; i < 3; i++ )
		{
			impact [ i ] = dl->origin [ i ] -
						   surf->plane->normal [ i ] * fdist;
		}

		local [ 0 ] = DotProduct( impact, tex->vecs [ 0 ] ) + tex->vecs [ 0 ] [ 3 ] - surf->texturemins [ 0 ];
		local [ 1 ] = DotProduct( impact, tex->vecs [ 1 ] ) + tex->vecs [ 1 ] [ 3 ] - surf->texturemins [ 1 ];

		pfBL = s_blocklights;

		for ( t = 0, ftacc = 0; t < tmax; t++, ftacc += 16 )
		{
			td = local [ 1 ] - ftacc;

			if ( td < 0 )
			{
				td = -td;
			}

			for ( s = 0, fsacc = 0; s < smax; s++, fsacc += 16, pfBL += 3 )
			{
				sd = Q_ftol( local [ 0 ] - fsacc );

				if ( sd < 0 )
				{
					sd = -sd;
				}

				if ( sd > td )
				{
					fdist = sd + ( td >> 1 );
				}
				else
				{
					fdist = td + ( sd >> 1 );
				}

				if ( fdist < fminlight )
				{
					pfBL [ 0 ] += ( frad - fdist ) * dl->color [ 0 ];
					pfBL [ 1 ] += ( frad - fdist ) * dl->color [ 1 ];
					pfBL [ 2 ] += ( frad - fdist ) * dl->color [ 2 ];
				}
			}
		}
	}
}

void
R_SetCacheState ( msurface_t *surf )
{
	int maps;

	for ( maps = 0; maps < MAXLIGHTMAPS && surf->styles [ maps ] != 255;
		  maps++ )
	{
		surf->cached_light [ maps ] = r_newrefdef.lightstyles [ surf->styles [ maps ] ].white;
	}
}

/*
 * Combine and scale multiple lightmaps into the floating format in blocklights
 */
void
R_BuildLightMap ( msurface_t *surf, byte *dest, int stride )
{
	int smax, tmax;
	int r, g, b, a, max;
	int i, j, size;
	byte        *lightmap;
	float scale [ 4 ];
	int nummaps;
	float       *bl;
	lightstyle_t    *style;

	if ( surf->texinfo->flags & ( SURF_SKY | SURF_TRANS33 | SURF_TRANS66 | SURF_WARP ) )
	{
		ri.Sys_Error( ERR_DROP, "R_BuildLightMap called for non-lit surface" );
	}

	smax = ( surf->extents [ 0 ] >> 4 ) + 1;
	tmax = ( surf->extents [ 1 ] >> 4 ) + 1;
	size = smax * tmax;

	if ( size > ( sizeof ( s_blocklights ) >> 4 ) )
	{
		ri.Sys_Error( ERR_DROP, "Bad s_blocklights size" );
	}

	/* set to full bright if no light data */
	if ( !surf->samples )
	{
		int maps;

		for ( i = 0; i < size * 3; i++ )
		{
			s_blocklights [ i ] = 255;
		}

		for ( maps = 0; maps < MAXLIGHTMAPS && surf->styles [ maps ] != 255;
			  maps++ )
		{
			style = &r_newrefdef.lightstyles [ surf->styles [ maps ] ];
		}

		goto store;
	}

	/* count the # of maps */
	for ( nummaps = 0; nummaps < MAXLIGHTMAPS && surf->styles [ nummaps ] != 255;
		  nummaps++ )
	{
	}

	lightmap = surf->samples;

	/* add all the lightmaps */
	if ( nummaps == 1 )
	{
		int maps;

		for ( maps = 0; maps < MAXLIGHTMAPS && surf->styles [ maps ] != 255;
			  maps++ )
		{
			bl = s_blocklights;

			for ( i = 0; i < 3; i++ )
			{
				scale [ i ] = gl_modulate->value * r_newrefdef.lightstyles [ surf->styles [ maps ] ].rgb [ i ];
			}

			if ( ( scale [ 0 ] == 1.0F ) &&
				 ( scale [ 1 ] == 1.0F ) &&
				 ( scale [ 2 ] == 1.0F ) )
			{
				for ( i = 0; i < size; i++, bl += 3 )
				{
					bl [ 0 ] = lightmap [ i * 3 + 0 ];
					bl [ 1 ] = lightmap [ i * 3 + 1 ];
					bl [ 2 ] = lightmap [ i * 3 + 2 ];
				}
			}
			else
			{
				for ( i = 0; i < size; i++, bl += 3 )
				{
					bl [ 0 ] = lightmap [ i * 3 + 0 ] * scale [ 0 ];
					bl [ 1 ] = lightmap [ i * 3 + 1 ] * scale [ 1 ];
					bl [ 2 ] = lightmap [ i * 3 + 2 ] * scale [ 2 ];
				}
			}

			lightmap += size * 3;     /* skip to next lightmap */
		}
	}
	else
	{
		int maps;

		memset( s_blocklights, 0, sizeof ( s_blocklights [ 0 ] ) * size * 3 );

		for ( maps = 0; maps < MAXLIGHTMAPS && surf->styles [ maps ] != 255;
			  maps++ )
		{
			bl = s_blocklights;

			for ( i = 0; i < 3; i++ )
			{
				scale [ i ] = gl_modulate->value * r_newrefdef.lightstyles [ surf->styles [ maps ] ].rgb [ i ];
			}

			if ( ( scale [ 0 ] == 1.0F ) &&
				 ( scale [ 1 ] == 1.0F ) &&
				 ( scale [ 2 ] == 1.0F ) )
			{
				for ( i = 0; i < size; i++, bl += 3 )
				{
					bl [ 0 ] += lightmap [ i * 3 + 0 ];
					bl [ 1 ] += lightmap [ i * 3 + 1 ];
					bl [ 2 ] += lightmap [ i * 3 + 2 ];
				}
			}
			else
			{
				for ( i = 0; i < size; i++, bl += 3 )
				{
					bl [ 0 ] += lightmap [ i * 3 + 0 ] * scale [ 0 ];
					bl [ 1 ] += lightmap [ i * 3 + 1 ] * scale [ 1 ];
					bl [ 2 ] += lightmap [ i * 3 + 2 ] * scale [ 2 ];
				}
			}

			lightmap += size * 3; /* skip to next lightmap */
		}
	}

	/* add all the dynamic lights */
	if ( surf->dlightframe == r_framecount )
	{
		R_AddDynamicLights( surf );
	}

store:

	stride -= ( smax << 2 );
	bl = s_blocklights;

	for ( i = 0; i < tmax; i++, dest += stride )
	{
		for ( j = 0; j < smax; j++ )
		{
			r = Q_ftol( bl [ 0 ] );
			g = Q_ftol( bl [ 1 ] );
			b = Q_ftol( bl [ 2 ] );

			/* catch negative lights */
			if ( r < 0 )
			{
				r = 0;
			}

			if ( g < 0 )
			{
				g = 0;
			}

			if ( b < 0 )
			{
				b = 0;
			}

			/* determine the brightest of the three color components */
			if ( r > g )
			{
				max = r;
			}
			else
			{
				max = g;
			}

			if ( b > max )
			{
				max = b;
			}

			/* alpha is ONLY used for the mono lightmap case.  For this reason
			   we set it to the brightest of the color components so that
			   things don't get too dim. */
			a = max;

			/* rescale all the color components if the intensity of the greatest
			   channel exceeds 1.0 */
			if ( max > 255 )
			{
				float t = 255.0F / max;

				r = r * t;
				g = g * t;
				b = b * t;
				a = a * t;
			}

			dest [ 0 ] = r;
			dest [ 1 ] = g;
			dest [ 2 ] = b;
			dest [ 3 ] = a;

			bl += 3;
			dest += 4;
		}
	}
}

